<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <title>bsr</title>
    <style>
        body {
            text-align: center;
        }

        svg {
            border: 1px solid #000;
        }

        .point {
            cursor: grab;
        }
    </style>
</head>
<body>
<svg id="canvas" height="500" width="500">
    <path id="bezier" fill="transparent" stroke="blue" stroke-width="2"/>
    <circle id="p0" class="point" cx="100" cy="250" fill="red" r="8"/>
    <circle id="p1" class="point" cx="200" cy="100" fill="green" r="8"/>
    <circle id="p2" class="point" cx="300" cy="400" fill="green" r="8"/>
    <circle id="p3" class="point" cx="400" cy="250" fill="red" r="8"/>
</svg>
<div>
    p0:<span id='p0v'></span><br/>
    p1:<span id='p1v'></span><br/>
    p2:<span id='p2v'></span><br/>
    p3:<span id='p3v'></span><br/>
</div>

<script>
    const canvas = document.getElementById("canvas");
    const points = document.querySelectorAll(".point");
    const bezierPath = document.getElementById("bezier");
    let selected = null;
    const p0input = document.getElementById("p0v");
    const p1input = document.getElementById("p1v");
    const p2input = document.getElementById("p2v");
    const p3input = document.getElementById("p3v");

    function bezierCubic(p0, p1, p2, p3, t) {
        const oneMinusT = 1 - t;
        return oneMinusT ** 3 * p0 +
            3 * oneMinusT ** 2 * t * p1 +
            3 * oneMinusT * t ** 2 * p2 +
            t ** 3 * p3;
    }

    function findTForX(xTarget, p0, p1, p2, p3) {
        let tMin = 0, tMax = 1, tMid;
        for (let i = 0; i < 20; i++) { // 二分查找提高精度
            tMid = (tMin + tMax) / 2;
            const x = bezierCubic(p0, p1, p2, p3, tMid);
            if (Math.abs(x - xTarget) < 0.5) return tMid;
            if (x < xTarget) tMin = tMid;
            else tMax = tMid;
        }
        return tMid;
    }

    function updatePath() {
        const [p0, p1, p2, p3] = [...points].map(p => [p.cx.baseVal.value, p.cy.baseVal.value]);
        let pathData = `M ${p0[0]},${p0[1]}`;

        p0input.innerText = p0[0].toFixed(1)
        p1input.innerText = p1[0].toFixed(1)
        p2input.innerText = p2[0].toFixed(1)
        p3input.innerText = p3[0].toFixed(1)

        for (let x = p0[0]; x <= p3[0]; x += 2) { // x 递增
            const t = findTForX(x, p0[0], p1[0], p2[0], p3[0]); // 反查 t
            const y = bezierCubic(p0[1], p1[1], p2[1], p3[1], t);
            pathData += ` L ${x},${y}`;
        }

        bezierPath.setAttribute("d", pathData);
    }

    points.forEach(point => {
        point.addEventListener("mousedown", e => {
            selected = e.target;
        });
    });

    document.addEventListener("mousemove", e => {
        if (selected) {
            const rect = canvas.getBoundingClientRect();
            selected.cx.baseVal.value = e.clientX - rect.left;
            selected.cy.baseVal.value = e.clientY - rect.top;
            updatePath();
        }
    });

    document.addEventListener("mouseup", () => selected = null);

    updatePath();
</script>
</body>
</html>
